package com.cloud.common.loadbalance;

import com.cloud.common.context.AppContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 服务实例状态管理
 *
 * @author fengwenjin
 */
@Slf4j
public class ServiceInstanceStatusManager {
    private static final String SEPARATOR = ":";
    private static Map<String, ServiceInstanceStatus> instanceStatusMap = new ConcurrentHashMap<>();
    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
    private static RestTemplate restTemplate;
    private static boolean executorRunning;

    static {
        SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        clientHttpRequestFactory.setConnectTimeout(3000);
        clientHttpRequestFactory.setReadTimeout(3000);
        restTemplate = new RestTemplate(clientHttpRequestFactory);
    }

    public static ServiceInstanceStatus get(ServiceInstance instance) {
        String key = getKey(instance);
        ServiceInstanceStatus instanceStatus = instanceStatusMap.get(key);
        if (instanceStatus == null) {
            instanceStatus = new ServiceInstanceStatus(instance);
            instanceStatusMap.put(key, instanceStatus);
            checkAlive();
        }
        return instanceStatus;
    }

    private static String getKey(ServiceInstance instance) {
        return instance.getHost() + SEPARATOR + instance.getPort();
    }

    private static synchronized void checkAlive() {
        if (executorRunning) {
            return;
        }
        executorRunning = true;

        // 定时检查服务实例
        executor.scheduleWithFixedDelay(() -> {
            Collection<ServiceInstanceStatus> instanceStatusList = instanceStatusMap.values();
            if (CollectionUtils.isEmpty(instanceStatusList)) {
                return;
            }
            ServiceInstanceListProvider provider = AppContext.getBean(ServiceInstanceListProvider.class);
            List<ServiceInstance> deletedInstanceList = new LinkedList<>();
            for (ServiceInstanceStatus instanceStatus : instanceStatusList) {
                // 检查实例是否存在
                ServiceInstance serviceInstance = instanceStatus.getServiceInstance();
                if (!provider.exist(serviceInstance)) {
                    deletedInstanceList.add(serviceInstance);
                    continue;
                }

                // 不熔断则不需要检查
                if (!instanceStatus.isCircuitBreakerTripped()) {
                    continue;
                }

                // 发送请求确认是否可用
                if (sendRequest(serviceInstance)) {
                    instanceStatus.clearConnectionFailureCount();
                } else {
                    instanceStatus.incrementConnectionFailureCount();
                }
            }
            // 删除不存在的实例
            for (ServiceInstance instance : deletedInstanceList) {
                instanceStatusMap.remove(getKey(instance));
            }
        }, 3, 5, TimeUnit.SECONDS);
    }

    private static boolean sendRequest(ServiceInstance serviceInstance) {
        String serverId = serviceInstance.getHost() + SEPARATOR + serviceInstance.getPort();
        try {
            String url = "http://" + serverId + "/actuator/info";
            ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
            if (response.getStatusCodeValue() == HttpStatus.OK.value()) {
                return true;
            }
        } catch (Exception e) {
            log.error("serverId={} unavailable", serverId);
        }
        return false;
    }
}
